import random
import math
class ItemBasedCF:
    def __init__(self,train_file,test_file):
        self.train_file = train_file
        self.test_file = test_file
        self.readData()

    def readData(self):
        #读取文件，并生成用户-物品的评分表和测试集
        self.train = dict()     #用户-物品的评分表
        for i in range(self.train_file.shape[0]):  
            user=self.train_file[i][0]   #userid train_file
            item=self.train_file[i][1]   #item_id 
            score=self.train_file[i][2]  #rating
            self.train.setdefault(user,{}) 
            self.train[user][item] = int(score)
            
        self.test = dict()      #测试集    
        for i in range(self.test_file.shape[0]):  
            user=self.test_file[i][0]   #userid train_file
            item=self.test_file[i][1]   #item_id 
            score=self.test_file[i][2]  #rating
            self.test.setdefault(user,{}) 
            self.test[user][item] = int(score)

    def ItemSimilarity(self):
        #建立物品-物品的共现矩阵
        C = dict()  #物品-物品的共现矩阵
        N = dict()  #物品被多少个不同用户购买
        for user,items in self.train.items():
            for i in items.keys():
                N.setdefault(i,0)
                N[i] += 1
                C.setdefault(i,{})
                for j in items.keys():
                    if i == j : continue
                    C[i].setdefault(j,0)
                    C[i][j] += 1
        #计算相似度矩阵
        self.W = dict()
        for i,related_items in C.items():
            self.W.setdefault(i,{})
            for j,cij in related_items.items():
                self.W[i][j] = cij / (math.sqrt(N[i] * N[j]))
        return self.W

    #给用户推荐前N个最感兴趣的物品
    def Recommend(self,user,K=3,N=10):
        rank = dict()
        action_item = self.train[user]     #用户user产生过行为的item和评分
        for item,score in action_item.items():
            for j,wj in sorted(self.W[item].items(),key=lambda x:x[1],reverse=True)[0:K]: #使用与物品item最相似的K个物品进行计算
                if j in action_item.keys():
                    continue
                rank.setdefault(j,0)
                rank[j] += score * wj
        return dict(sorted(rank.items(),key=lambda x:x[1],reverse=True)[0:N])
    
    def RecallAndPrecision(self,train=None,test=None,K=40,N=20):
        train = train or self.train
        test = test or self.test
        hit = 0
        recall = 0
        precision = 0
        for user in train.keys():
            tu = test.get(user,{})
            rank = self.Recommend(user,K=K,N=N)
            for i,_ in rank.items():
                if i in tu:
                   hit += 1
            recall += len(tu)
            precision += N
        recall = hit / (recall * 1.0)
        precision = hit / (precision * 1.0)
        return (recall,precision)
